/*
 * The MIT License
 *
 * Copyright 2014 David Rodenas Pico.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package edu.uoc.sd.fr14.comp;

import edu.uoc.sd.fr14.actors.Customer;
import edu.uoc.sd.fr14.CustomerConfiguration;
import edu.uoc.sd.fr14.imesg.SeatMessage;
import edu.uoc.sd.fr14.mesg.ConfirmMessage;
import edu.uoc.sd.fr14.mesg.Message;
import edu.uoc.sd.fr14.mesg.MessageForCustomer;
import edu.uoc.sd.fr14.mesg.QueryMessage;
import edu.uoc.sd.fr14.model.CustomerId;
import edu.uoc.sd.fr14.model.FlightId;
import edu.uoc.sd.fr14.model.Logger;
import edu.uoc.sd.fr14.model.ServerId;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.TimerTask;

/**
 *
 * @author drodenas
 */
public class CustomerComponent extends Component {
    
    private static final Logger log = Logger.get("CustomerComponent");
    
    private CustomerId me;
    private Customer customer;
    private final Set<ServerId> servers;
    private final CustomerConfiguration customerConfiguration;
    private final Random random = new Random();
    private final Stats stats = new Stats();
    
    public CustomerComponent(final CustomerId me, Set<ServerId> servers, CustomerConfiguration customerConfiguration) {
        super("" + me);
        if (me == null) throw new NullPointerException("Illegal argument: me cannot be null");
        if (servers == null) throw new NullPointerException("Illegal argument: servers cannot be null");
        if (servers.isEmpty()) throw new IllegalArgumentException("Illegal argument: servers should have at least one server");
        if (customerConfiguration == null) throw new NullPointerException("Illegal argument: customerConfiguration cannot be null");
        
        this.me = me;
        this.servers = servers;
        this.customerConfiguration = customerConfiguration;
        
        // the customer dows nothing
        customer = new Customer() {
            @Override
            public void confirmed(FlightId flight, CustomerId customer, boolean value) {
            	stats.confirmed();
            }

            @Override
            public void queried(FlightId flight, int freeSeats) {
            	stats.queried();
            }
        };
        
        // the timer task
        getTimer().schedule(new TimerTask() {
            @Override
            public void run() {
                tick();
            }
        }, 0, customerConfiguration.getActivityTo());
    }
    
    public Stats getStats() {
		return stats;
	}

    @Override
    protected void process(Message message) {
        Customer current = customer;

        if (message == null || current == null) return;                
        if (!message.getTarget().equals(me)) return;
        if (!(message instanceof MessageForCustomer)) return;

        log.debug(me, "applying message '"+message+"'");
        ((MessageForCustomer) message).apply(current);
    }

    

    private void tick() {
        
        log.debug(me, "performing tick");
        stats.tick();
        if (random.nextDouble() < customerConfiguration.getSeatProbability()) {
            generateSeat();
        }
        if (random.nextDouble() < customerConfiguration.getConfirmProbability()) {
            generateConfirm();
        }
        if (random.nextDouble() < customerConfiguration.getQueryProbability()) {
            generateQuery();
        }
    }
  
    private final List<ServerId> nextServers = new ArrayList<>();
    private ServerId randomServer() {
        
        if (nextServers.isEmpty()) {
            nextServers.addAll(servers);
            Collections.shuffle(nextServers, random);
        }
        
        ServerId randomServer = nextServers.remove(0);
        return randomServer;
    }
    
    private int dataNextFlight = 0;
    private FlightId nextFlight() {
        
        dataNextFlight = dataNextFlight + 1;
        int code = random.nextInt(3 + dataNextFlight / 13);
        FlightId nextFlight = new FlightId(code);
        return nextFlight;
    }
    
    private void generateSeat() {        
    	stats.seat();
    	
        ServerId target = randomServer();
        FlightId flight = nextFlight();
        
        Message message = new SeatMessage(target, flight, me, me);
        deliver(message);
    }

    private void generateConfirm() {        
    	stats.confirm();
    	
        ServerId target = randomServer();
        FlightId flight = nextFlight();

        Message message = new ConfirmMessage(target, flight, me);
        deliver(message);
    }

    private void generateQuery() {
    	stats.query();
        
        ServerId target = randomServer();
        FlightId flight = nextFlight();        

        Message message = new QueryMessage(target, flight, me);
        deliver(message);
    }

    @Override
    protected void deliver(Message message) {
        log.debug(me, "deliver '"+message+"'");
        super.deliver(message);
    }
    
    
    public static final class Stats implements Serializable {
		private static final long serialVersionUID = 1229119437929686245L;
		
		private int confirmCount;
    	private int confirmedCount;
    	private int queriedCount;
    	private int queryCount;
    	private int seatCount;
    	private int tickCount;
    	
		public void confirm() {
			confirmCount++;
		}
		public void confirmed() {
			confirmedCount++;
		}
		public void queried() {
			queriedCount++;
		}
		public void query() {
			queryCount++;
		}
		public void seat() {
			seatCount++;	
		}
		public void tick() {
			tickCount++;
		}

		public int getConfirmCount() {
			return confirmCount;
		}		
		public int getConfirmedCount() {
			return confirmedCount;
		}
		public int getQueriedCount() {
			return queriedCount;
		}
		public int getQueryCount() {
			return queryCount;
		}
		public int getSeatCount() {
			return seatCount;
		}
		public int getTickCount() {
			return tickCount;
		}
		@Override
		public String toString() {
			return "Stats [confirmCount=" + confirmCount + ", confirmedCount="
					+ confirmedCount + ", queriedCount=" + queriedCount
					+ ", queryCount=" + queryCount + ", seatCount=" + seatCount
					+ ", tickCount=" + tickCount + "]";
		}
		
		
		
    };
    
}
